package com.dingwen.treasure.manage.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dingwen.treasure.base.enums.StatusEnum;
import com.dingwen.treasure.base.exception.AuthException;
import com.dingwen.treasure.model.entity.mybatisplus.SysElement;
import com.dingwen.treasure.model.entity.mybatisplus.SysMenu;
import com.dingwen.treasure.model.entity.mybatisplus.SysUser;
import com.dingwen.treasure.mybatisplus.service.SysElementService;
import com.dingwen.treasure.mybatisplus.service.SysMenuService;
import com.dingwen.treasure.mybatisplus.service.SysUserService;
import com.dingwen.treasure.security.dto.LoginUser;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 　UserDetailsServiceImpl
 * 　@author dingwen
 * 　@date 2022/6/12
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    /**
     * sysUserService
     */
    @Resource
    private SysUserService sysUserService;

    /**
     * sysElementService
     */
    @Resource
    private SysElementService sysElementService;


    /**
     * sysMenuService
     */
    @Resource
    private SysMenuService sysMenuService;

    /**
     * Locates the user based on the username. In the actual implementation, the search
     * may possibly be case-sensitive, or case insensitive depending on how the
     * implementation instance is configured. In this case, the <code>UserDetails</code>
     * object that comes back may have a username that is of a different case than what
     * was actually requested..
     *
     * @param username the username identifying the user whose data is required.
     * @return a fully populated user record (never <code>null</code>)
     * @throws UsernameNotFoundException if the user could not be found or the user has no
     *                                   GrantedAuthority
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 通过用户名查找用户 （已经开启逻辑删除功能）
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysUser::getAccount, username);
        SysUser sysUser = sysUserService.getOne(wrapper);

        if (ObjectUtils.isEmpty(sysUser)) {
            String message = StrUtil.format("用户名：{},不存在", username);
            throw new AuthException(HttpStatus.UNAUTHORIZED.value(), message);
        }

        if (sysUser.getStatus().equals(StatusEnum.DEACTIVATE.getCode())) {
            String message = StrUtil.format("用户名：{},已停用", username);
            throw new AuthException(HttpStatus.LOCKED.value(), message);
        }

        // 构建登录用户信息：
        return buildLoginUser(sysUser);
    }


    /**
     * 构建一个登录用户
     *
     * @param sysUser
     * @return
     */
    private UserDetails buildLoginUser(SysUser sysUser) {
        // 用户Id
        Long id = sysUser.getId();
        Assert.notNull(id, "用户Id不能为空");


        // 菜单
        List<SysMenu> menus = sysMenuService.getSysMenusByUserId(id);

        // 元素
        List<SysElement> elements = sysElementService.getSysElementsByUserId(id);

        // 权限信息
        Set<String> menuPermissions = menus.parallelStream()
                .map(SysMenu::getPerms)
                .collect(Collectors.toSet());

        Set<String> elementsPermissions = elements.parallelStream()
                .map(SysElement::getPerms)
                .collect(Collectors.toSet());

        // 所有权限信息
        HashSet<String> allPermissions = CollUtil.newHashSet(menuPermissions);
        allPermissions.addAll(elementsPermissions);

        return LoginUser.builder()
                .user(sysUser)
                .userId(id)
                .permissions(allPermissions)
                .build();
    }
}
